package virtualinit;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import javafx.beans.binding.When;
import virtualinit.util.CommonUtil;
import virtualinit.util.Log;

import javax.sound.midi.Track;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

public class Config {
    public static final String configPrefix = "config.";
    public Set<String> channelBlackList = new HashSet<>();
    public Set<String> channelWhiteList = new HashSet<>();
    Log log = Log.instance;
    public static String defaultChannelName = "default";
    public String storageDomainKey = "storage";
    public String scheduleDomainKey = "schedule";
    public String saleDomainKey = "sale";

    private Config() {}


    public static final Config instance = new Config();

    static {
        try {
            instance.init();
        } catch (Exception e) {
            throw new RuntimeException("配置初始化失败：" + e.getMessage(), e);
        }
    }


    Properties properties = new Properties();

    public String env;
    public int batchSize = 1000;
    public int tipsSize = 1000000;
    public int threadPoolSize = 4;
    public int sqlQueueSize = 1;
    public String wastonDbPrefix = "tf-domain-";

    public List<Ds> dsList = new ArrayList<>(16);
    public transient Map<String, Ds> nameToDs = new HashMap<>(16);
    public transient Map<String, Ds> channelToDs = new HashMap<>(16);
    public transient Map<String, Ds> domainToDs = new HashMap<>(16);
    public transient Map<String, Ds> dbNameToDs = new HashMap<>(16);
    public Map<String, String> domainDbNameMapping;
    public Map<String, String> domainDsNameMapping;
    public Map<String, String> channelDsNameMapping;

    public InitVirtualStockConfig initVirtualStockConfig;

    {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> nameToDs.values().forEach(e -> e.close())));
    }

    public static void main(String[] args) throws Exception {
        System.setProperty("env", "prod");
        new Config().init();
    }

    public void init() throws Exception {
        // 1.先加载app.properties获取环境变量和基础配置
        env = getProperty("env");
        properties.load(new InputStreamReader(Config.class.getResourceAsStream("/app.properties"), StandardCharsets.UTF_8));
        if (env == null) env = properties.getProperty("env");
        CommonUtil.assertIsEmpty("env=null", env);
        log.info("当前激活环境：" + env);
        log.info("加载默认配置文件：app.properties\n{}", prettyJson(properties));

        // 2.加载指定env的配置文件
        Properties p = new Properties();
        p.load(new InputStreamReader(Config.class.getResourceAsStream("/app_" + env + ".properties"), StandardCharsets.UTF_8));
        log.info("加载配置文件：app_{}.properties\n{}", env, prettyJson(p));
        properties.putAll(p);

        //  从-D参数获取
        properties.keySet().forEach(e -> properties.put(e, getProperty((String) e)));

        // 成员变量基本属性解析
        this.storageDomainKey = getProperty(configPrefix + "storageDomainKey", storageDomainKey);
        this.scheduleDomainKey = getProperty(configPrefix + "storageDomainKey", scheduleDomainKey);
        this.saleDomainKey = getProperty(configPrefix + "saleDomainKey", saleDomainKey);
        this.batchSize = getInteger(configPrefix + "batchSize", batchSize);
        this.tipsSize = getInteger(configPrefix + "tipsSize", tipsSize);
        this.threadPoolSize = getInteger(configPrefix + "threadPoolSize", threadPoolSize);
        this.sqlQueueSize = getInteger(configPrefix + "sqlQueueSize", sqlQueueSize);
        this.wastonDbPrefix = getProperty(configPrefix + "wastonDbPrefix", wastonDbPrefix);

        Map<Integer, Map<String, String>> idToDsConfig = new HashMap<>();

        // 解析datasource 先不初始化
        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if (!key.startsWith(configPrefix)) continue;

            key = key.replace(configPrefix, "");

            String dsPrefix = "ds";
            if (key.startsWith(dsPrefix)) {
                // ds后面到 . 之间都是数字
                try {
                    int endIndex = key.indexOf('.', dsPrefix.length());
                    Integer id = Integer.parseInt(key.substring(dsPrefix.length(), endIndex));
                    Map<String, String> config = idToDsConfig.computeIfAbsent(id, k -> new HashMap<>());
                    config.put(key.substring(endIndex + 1), value);
                } catch (Exception e) {
                }
            }

            // 初始化domain to ds mapping
            // domain-db mapping 查询的时候还是要有域的概念，都是从域查
            // channel-ds-mapping
            if (key.equals("channelDsNameMapping")) {
                channelDsNameMapping = stringToMap(value);
            } else if (key.equals("domainDbNameMapping")) {
                domainDbNameMapping = stringToMap(value);
            } else if (key.equals("domainDsNameMapping")) {
                domainDsNameMapping = stringToMap(value);
            }


        }

        CommonUtil.assertIsEmpty(configPrefix + "channelDsNameMapping=null", channelDsNameMapping);
        CommonUtil.assertIsEmpty(configPrefix + "domainDbNameMapping=null", domainDbNameMapping);
        CommonUtil.assertIsEmpty(configPrefix + "domainDsNameMapping=null", domainDsNameMapping);


        // datasource config 封装成ds
        for (Map.Entry<Integer, Map<String, String>> e : idToDsConfig.entrySet()) {
            Integer id = e.getKey();
            Map<String, String> config = e.getValue();
            config.put("id", id.toString());
            Ds ds = toDs(config);
            dsList.add(ds);
            nameToDs.put(ds.name, ds);
        }

        channelDsNameMapping.forEach((channel, dsName) -> {
            CommonUtil.assertIsEmpty(configPrefix + "channel-ds-mapping：不存在这样的dsName：" + dsName, nameToDs.get(dsName));
            channelToDs.put(channel, nameToDs.get(dsName));
        });
        domainDsNameMapping.forEach((domain, dsName) -> {
            CommonUtil.assertIsEmpty(configPrefix + "domain-ds-mapping：不存在这样的dsName：" + dsName, nameToDs.get(dsName));
            domainToDs.put(domain, nameToDs.get(dsName));
        });


        String whiteList = getProperty("config.channelWhiteList");
        String blackList = getProperty("config.channelBlackList");
        if (whiteList != null) {
            for (String channel : whiteList.split(",")) {
                String trim = channel.trim();
                if (trim.length() != 0) channelWhiteList.add(trim);
            }
        }
        if (blackList != null) {
            for (String channel : blackList.split(",")) {
                String trim = channel.trim();
                if (trim.length() != 0) channelBlackList.add(trim);
            }
        }
        log.info("初始化配置完毕：\n{}", prettyJson(this));

    }

    private Map<String, String> stringToMap(String str) {
        return Arrays.stream(str.trim().split(","))
                .collect(Collectors.toMap(e -> e.trim().split(":")[0], e -> e.trim().split(":")[1]));
    }


    public static String prettyJson(Object obj) {
        return JSON.toJSONString(obj, SerializerFeature.PrettyFormat);
    }

    private Ds toDs(Map<String, String> map) {
        Ds ds = new Ds();
        ds.id = map.get("id");
        ds.name = map.get("name");
        ds.url = map.get("url");
        ds.username = map.get("username");
        ds.password = map.get("password");
        return ds;
    }

    public String getProperty(String key) {
        String property = System.getProperty(key);
        if (CommonUtil.isEmpty(property)) {
            property = System.getenv().get(key);
        }

        if (CommonUtil.isEmpty(property)) {
            property = properties.getProperty(key);
        }
        return property;
    }

    public Integer getInteger(String key, Integer defaultVal) {
        String v = getProperty(key);
        if (v == null) return defaultVal;
        return Integer.parseInt(v);
    }

    public String getProperty(String key, String defaultVal) {
        String property = getProperty(key);
        return CommonUtil.isEmpty(property) ? defaultVal : property;
    }

    public void initVirtualStockConfig() {
        InitVirtualStockConfig c = new InitVirtualStockConfig();
        c.defaultChannelDbName = getProperty(configPrefix + "defaultChannelDbName", c.defaultChannelDbName);
        c.defaultChannelDs = this.channelToDs.get(defaultChannelName);
        c.subDatabasePrefix = getProperty(configPrefix + "subDatabasePrefix", c.subDatabasePrefix);
        c.subTablePrefix = getProperty(configPrefix + "subTablePrefix", c.subTablePrefix);
        c.loadDsTableLimit = getInteger(configPrefix + "loadDsTableLimit", c.loadDsTableLimit);
        this.initVirtualStockConfig = c;
    }

    public void initDs(int i) {
        this.nameToDs.values().forEach(e -> e.init(i));
    }

    public void initDbToDs() {
        log.info("数据库和数据源映射关系：\n");
        for (Ds ds : this.nameToDs.values()) {
            ds.initAllDsInfos();
            for (Ds.Database database : ds.databases) {
                dbNameToDs.put(database.databaseName, ds);
            }
        }

    }

    public boolean channelForbidden(String channelId) {
        if (channelWhiteList.isEmpty()) {
            // if (channelBlackList.isEmpty()) return false;
            // if (channelBlackList.contains(channelId)) return true;
            return false;
        }
        return !channelWhiteList.contains(channelId);
    }


    // init.virtual.stock
    public static class InitVirtualStockConfig {
        public String defaultChannelDbName = "tf-domain-schedule"; // 默认channel所在db
        public Ds defaultChannelDs; // 默认channel的数据源
        public String subDatabasePrefix; // 分库prefix
        public String subTablePrefix; // 分表prefix
        public int loadDsTableLimit = -1; //数据源加载条数限制（每个表）

        public String getLimit() {
            if (loadDsTableLimit > 0) return " limit " + loadDsTableLimit;
            return "";
        }
    }
}
